﻿
using System.Data.EntityClient;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Security.Permissions;
using System.Threading;
using System.Web.Configuration;

namespace RouteTiming.SLClient.Web
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Data;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.DomainServices.EntityFramework;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;
    using RouteTiming.Data;


    // Implements application logic using the RouteTimingContext context.
    // TODO: Add your application logic to these methods or in additional methods.
    // TODO: Wire up authentication (Windows/ASP.NET Forms) and uncomment the following to disable anonymous access
    // Also consider adding roles to restrict access as appropriate.
    // [RequiresAuthentication]
    [EnableClientAccess()]
    public class RouteTimingDomainService : LinqToEntitiesDomainService<RouteTimingContext>
    {
        private const int PollingTimeoutInSeconds = 5;

        private readonly ManualResetEvent _evtChange = new ManualResetEvent(false);

        /// <summary>
        /// A helper method, that uses Sqlependency to wait for new events to appear in the DB
        /// </summary>
        /// <param name="lastEventOrderNo"></param>
        /// <param name="timeoutInSeconds"></param>
        private void WaitForNewEvents(Int64 lastEventOrderNo)
        {
            _evtChange.Reset();

            // converting EF connection string to ADO.Net connection string
            var connStringBuilder = new EntityConnectionStringBuilder(WebConfigurationManager.ConnectionStrings["RouteTimingContext"].ConnectionString);
            string connString = connStringBuilder.ProviderConnectionString;

            // waiting for newly added Events
            string sqlQuery = "select [Id] from [dbo].[EventSet] where [OrderNo] > " + lastEventOrderNo;

            SqlDependency.Start(connString);
            try
            {
                using (var conn = new SqlConnection(connString))
                {
                    conn.Open();
                    var cmd = new SqlCommand(sqlQuery, conn);

                    var dependency = new SqlDependency(cmd);
                    dependency.OnChange += onChangeHandler;

                    cmd.ExecuteReader();

                    this._evtChange.WaitOne(TimeSpan.FromSeconds(PollingTimeoutInSeconds));
                    dependency.OnChange -= onChangeHandler;
                }
            }
            catch
            {
                // don't let the client dos-attacking us by calling the method too often
                Thread.Sleep(TimeSpan.FromSeconds(PollingTimeoutInSeconds));
            }
            finally 
            {
                SqlDependency.Stop(connString);
            }
        }

        void onChangeHandler(object sender, SqlNotificationEventArgs e)
        {
            if (e.Info == SqlNotificationInfo.Insert)
            {
                this._evtChange.Set();
            }
        }

        /// <summary>
        /// Gets newly appeared events, implements long polling
        /// </summary>
        /// <param name="lastEventOrderNo"></param>
        /// <returns></returns>
        [Invoke(HasSideEffects = true)]
        public IEnumerable<Event> GetNewEvents(Int64 lastEventOrderNo)
        {
            try
            {
                this.WaitForNewEvents(lastEventOrderNo);
            }
            catch (Exception ex)
            {
                Trace.TraceError("Tried to wait for new events using SqlDependency. The following error occured: {0}", ex);
            }

            return from e in this.ObjectContext.EventSet
                   where e.OrderNo > lastEventOrderNo
                   orderby e.OrderNo
                   select e;
        }


        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'EventSet' query.
        public IQueryable<Event> GetEventSet()
        {
            return this.ObjectContext.EventSet;
        }

        public void InsertEvent(Event @event)
        {
            if ((@event.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(@event, EntityState.Added);
            }
            else
            {
                this.ObjectContext.EventSet.AddObject(@event);
            }
        }

        public void UpdateEvent(Event currentEvent)
        {
            this.ObjectContext.EventSet.AttachAsModified(currentEvent, this.ChangeSet.GetOriginal(currentEvent));
        }

        public void DeleteEvent(Event @event)
        {
            if ((@event.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(@event, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.EventSet.Attach(@event);
                this.ObjectContext.EventSet.DeleteObject(@event);
            }
        }

        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'PersonSet' query.
        public IQueryable<Person> GetPersonSet()
        {
            return this.ObjectContext.PersonSet;
        }

        public void InsertPerson(Person person)
        {
            if ((person.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(person, EntityState.Added);
            }
            else
            {
                this.ObjectContext.PersonSet.AddObject(person);
            }
        }

        public void UpdatePerson(Person currentPerson)
        {
            this.ObjectContext.PersonSet.AttachAsModified(currentPerson, this.ChangeSet.GetOriginal(currentPerson));
        }

        public void DeletePerson(Person person)
        {
            if ((person.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(person, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.PersonSet.Attach(person);
                this.ObjectContext.PersonSet.DeleteObject(person);
            }
        }

        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'RouteSet' query.
        public IQueryable<Route> GetRouteSet()
        {
            return this.ObjectContext.RouteSet.Include("RoutePoints");
        }

        public void InsertRoute(Route route)
        {
            if ((route.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(route, EntityState.Added);
            }
            else
            {
                this.ObjectContext.RouteSet.AddObject(route);
            }
        }

        public void UpdateRoute(Route currentRoute)
        {
            this.ObjectContext.RouteSet.AttachAsModified(currentRoute, this.ChangeSet.GetOriginal(currentRoute));
        }

        public void DeleteRoute(Route route)
        {
            if ((route.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(route, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.RouteSet.Attach(route);
                this.ObjectContext.RouteSet.DeleteObject(route);
            }
        }

        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'RoutePointSet' query.
        public IQueryable<RoutePoint> GetRoutePointSet()
        {
            return this.ObjectContext.RoutePointSet;
        }

        public void InsertRoutePoint(RoutePoint routePoint)
        {
            if ((routePoint.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(routePoint, EntityState.Added);
            }
            else
            {
                this.ObjectContext.RoutePointSet.AddObject(routePoint);
            }
        }

        public void UpdateRoutePoint(RoutePoint currentRoutePoint)
        {
            this.ObjectContext.RoutePointSet.AttachAsModified(currentRoutePoint, this.ChangeSet.GetOriginal(currentRoutePoint));
        }

        public void DeleteRoutePoint(RoutePoint routePoint)
        {
            if ((routePoint.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(routePoint, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.RoutePointSet.Attach(routePoint);
                this.ObjectContext.RoutePointSet.DeleteObject(routePoint);
            }
        }

        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'RouteScheduleSet' query.
        public IQueryable<RouteSchedule> GetRouteScheduleSet()
        {
            return this.ObjectContext.RouteScheduleSet;
        }

        public void InsertRouteSchedule(RouteSchedule routeSchedule)
        {
            if ((routeSchedule.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(routeSchedule, EntityState.Added);
            }
            else
            {
                this.ObjectContext.RouteScheduleSet.AddObject(routeSchedule);
            }
        }

        public void UpdateRouteSchedule(RouteSchedule currentRouteSchedule)
        {
            this.ObjectContext.RouteScheduleSet.AttachAsModified(currentRouteSchedule, this.ChangeSet.GetOriginal(currentRouteSchedule));
        }

        public void DeleteRouteSchedule(RouteSchedule routeSchedule)
        {
            if ((routeSchedule.EntityState != EntityState.Detached))
            {
                this.ObjectContext.ObjectStateManager.ChangeObjectState(routeSchedule, EntityState.Deleted);
            }
            else
            {
                this.ObjectContext.RouteScheduleSet.Attach(routeSchedule);
                this.ObjectContext.RouteScheduleSet.DeleteObject(routeSchedule);
            }
        }
    }
}


